package com.gw2.gw2world.controller;

import java.io.Serializable;

import com.gw2.gw2world.R;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.ImageView;

/** This seekbar is base on an another one available here: https://code.google.com/p/range-seek-bar/
 * I removed unused functions like type support (I only need integer)
 * @author Seb
 */
public class RangeSeekbar extends ImageView implements Serializable {
	private static final long serialVersionUID = 1L;
	@SuppressWarnings("unused")
	private static final String TAG="RangeSeekbar";
	private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	//thumb
	private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_normal);
	private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_pressed);
	private final int thumbWidth = thumbImage.getWidth();
	private final int thumbHalfWidth =thumbWidth/2;
	private final int thumbHalfHeight = thumbImage.getHeight()/2;
	private final int lineHeight = Math.round(0.3f * ((float)thumbHalfHeight));
	private final int padding = thumbHalfWidth;
	private Thumb pressedThumb = null;
	//displayed values
	private int minValue=1, maxValue=80;
	private int minSelectableValue=1, maxSelectableValue=80;
	private int minSelectedValue=1, maxSelectedValue=80;
	private boolean notifyWhileDragging = false;
	private OnRangeSeekBarChangeListener listener;

	/** An invalid pointer id. */
	public static final int INVALID_POINTER_ID = 255;

	// Localized constants from MotionEvent for compatibility with API < 8 "Froyo".
	public static final int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
	private float mDownMotionX;
	private int mActivePointerId = INVALID_POINTER_ID;

	/**
	 * On touch, this offset plus the scaled value from the position of the
	 * touch will form the progress value. Usually 0.
	 */
	float mTouchProgressOffset;

	private int mScaledTouchSlop;
	private boolean mIsDragging;

	/** Creates a new RangeSeekBar.
	 * @param min The minimum value of the selectable range.
	 * @param min The maximum value of the selectable range.
	 * @param context
	 */
	public RangeSeekbar(Context context, AttributeSet attrs) {
		super(context, attrs);
		// make RangeSeekBar focusable. This solves focus handling issues in
		// case EditText widgets are being used along with the RangeSeekBar
		// within ScollViews.
		setFocusable(true);
		setFocusableInTouchMode(true);
		init();
	}

	private final void init() {mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();}
	public boolean isNotifyWhileDragging() {return notifyWhileDragging;}
	/** Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
	 * @param flag */
	public void setNotifyWhileDragging(boolean flag) {this.notifyWhileDragging = flag;}
	/** Registers given listener callback to notify about changed selected values.
	 * @param listener The listener to notify about changed selected values. */
	public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener listener) {this.listener = listener;}
	/** Handles thumb selection and movement. Notifies listener callback on certain events. */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!isEnabled()) return false;
		int pointerIndex;
		final int action = event.getAction();
		switch (action & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			// Remember where the motion event started
			mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
			pointerIndex = event.findPointerIndex(mActivePointerId);
			mDownMotionX = event.getX(pointerIndex);
			pressedThumb = evalPressedThumb(mDownMotionX);
			// Only handle thumb presses.
			if (pressedThumb == null) return super.onTouchEvent(event);
			setPressed(true);
			invalidate();
			onStartTrackingTouch();
			trackTouchEvent(event);
			attemptClaimDrag();
			break;
		case MotionEvent.ACTION_MOVE:
			if (pressedThumb != null) {
				if (mIsDragging) {
					trackTouchEvent(event);
				} else {
					// Scroll to follow the motion event
					pointerIndex = event.findPointerIndex(mActivePointerId);
					final float x = event.getX(pointerIndex);
					if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
						setPressed(true);
						invalidate();
						onStartTrackingTouch();
						trackTouchEvent(event);
						attemptClaimDrag();
					}
				}
				invalidate();
				if (/*notifyWhileDragging &&*/ listener != null) {
					listener.onRangeSeekBarValuesChanged(this,minSelectedValue, maxSelectedValue);
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (mIsDragging) {
				trackTouchEvent(event);
				onStopTrackingTouch();
				setPressed(false);
			} else {
				// Touch up when we never crossed the touch slop threshold
				// should be interpreted as a tap-seek to that location.
				onStartTrackingTouch();
				trackTouchEvent(event);
				onStopTrackingTouch();
			}

			pressedThumb = null;
			invalidate();
			if (listener != null)
				listener.onRangeSeekBarValuesChanged(this, minSelectedValue, maxSelectedValue);
			break;
		case MotionEvent.ACTION_POINTER_DOWN: {
			final int index = event.getPointerCount() - 1;
			// final int index = ev.getActionIndex();
			mDownMotionX = event.getX(index);
			mActivePointerId = event.getPointerId(index);
			invalidate();
			break;
		}
		case MotionEvent.ACTION_POINTER_UP:
			onSecondaryPointerUp(event);
			invalidate();
			break;
		case MotionEvent.ACTION_CANCEL:
			if (mIsDragging) {
				onStopTrackingTouch();
				setPressed(false);
			}
			invalidate();
			break;
		}
		return true;
	}

	private final void onSecondaryPointerUp(MotionEvent ev) {
		final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;

		final int pointerId = ev.getPointerId(pointerIndex);
		if (pointerId == mActivePointerId) {
			// This was our active pointer going up. Choose a new active pointer and adjust accordingly.
			final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
			mDownMotionX = ev.getX(newPointerIndex);
			mActivePointerId = ev.getPointerId(newPointerIndex);
		}
	}

	private final void trackTouchEvent(MotionEvent event) {
		final int pointerIndex = event.findPointerIndex(mActivePointerId);
		final float x = event.getX(pointerIndex);
		if (Thumb.MIN.equals(pressedThumb))
			minSelectedValue=screenToValue(x);
		else if (Thumb.MAX.equals(pressedThumb))
			maxSelectedValue=screenToValue(x);
		//prevent min to be greater than max
		if(minSelectedValue>maxSelectedValue)
			minSelectedValue=maxSelectedValue;
		/*if(maxSelectableValue<minSelectedValue)
			maxSelectableValue=minSelectedValue;*/
	}

	/** Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag. */
	private void attemptClaimDrag() {
		if (getParent() != null)
			getParent().requestDisallowInterceptTouchEvent(true);
	}
	/** This is called when the user has started touching this widget. */
	void onStartTrackingTouch() {mIsDragging = true;}
	/** This is called when the user either releases his touch or the touch is canceled. */
	void onStopTrackingTouch() { mIsDragging = false; }
	/** Ensures correct size of the widget. */
	@Override
	protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int width = 200;
		if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec))
			width = MeasureSpec.getSize(widthMeasureSpec);
		int height = thumbImage.getHeight();
		if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec))
			height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
		setMeasuredDimension(width, height);
	}
	
	/** Draws the widget on the given canvas. */
	@SuppressLint("DrawAllocation")
	@Override
	protected synchronized void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		final RectF rect=new RectF(padding,
				(getHeight() - lineHeight)/2, getWidth() - padding,
				(getHeight() + lineHeight)/2);
		// draw seek bar background line
		paint.setStyle(Style.FILL);
		paint.setColor(Color.GRAY);
		paint.setAntiAlias(true);
		canvas.drawRect(rect, paint);
		// draw selectable bar
		paint.setColor(getResources().getColor(android.R.color.darker_gray));
		rect.left = valueToScreen(minSelectableValue);
		rect.right= valueToScreen(maxSelectableValue);
		canvas.drawRect(rect, paint);
		// draw seek bar active range line
		rect.left = valueToScreen(minSelectedValue);
		rect.right = valueToScreen(maxSelectedValue);
		// Range color, #FF33B5E5. This is also known as "Ice Cream Sandwich" blue.
		paint.setColor(0xff33b5e5);
		canvas.drawRect(rect, paint);
		// draw minimum thumb
		drawThumb(valueToScreen(minSelectedValue), Thumb.MIN.equals(pressedThumb), canvas);
		// draw maximum thumb
		drawThumb(valueToScreen(maxSelectedValue), Thumb.MAX.equals(pressedThumb), canvas);
		
		/*android.util.Log.d(TAG, "Range: "+String.valueOf(minValue)+" - "+String.valueOf(maxValue));
		android.util.Log.d(TAG, "Selectable range: "+String.valueOf(minSelectableValue)+" - "+String.valueOf(maxSelectableValue));
		android.util.Log.d(TAG, "Thumbs: "+String.valueOf(minSelectedValue)+" - "+String.valueOf(maxSelectedValue));*/
	}

	/** Overridden to save instance state when device orientation changes. This
	 * method is called automatically if you assign an id to the RangeSeekBar
	 * widget using the {@link #setId(int)} method. Other members of this class
	 * than the normalized min and max values don't need to be saved.
	 */
	@Override
	protected Parcelable onSaveInstanceState() {
		final Bundle bundle = new Bundle();
		bundle.putParcelable("SUPER", super.onSaveInstanceState());
		bundle.putInt("MIN", minValue);
		bundle.putInt("MAX", maxValue);
		return bundle;
	}

	/** Overridden to restore instance state when device orientation changes.
	 * This method is called automatically if you assign an id to the
	 * RangeSeekBar widget using the {@link #setId(int)} method.
	 */
	@Override
	protected void onRestoreInstanceState(Parcelable parcel) {
		final Bundle bundle = (Bundle) parcel;
		super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
		minValue = bundle.getInt("MIN");
		maxValue = bundle.getInt("MAX");
	}

	/** Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
	 * @param screenCoord The x-coordinate in screen space where to draw the image.
	 * @param pressed Is the thumb currently in "pressed" state?
	 * @param canvas The canvas to draw upon.
	 */
	private void drawThumb(float screenCoord, boolean pressed, Canvas canvas) {
		canvas.drawBitmap(pressed ? thumbPressedImage : thumbImage, screenCoord - thumbHalfWidth, (float) ((getHeight()/2) - thumbHalfHeight), paint);
	}

	/** Decides which (if any) thumb is touched by the given x-coordinate.
	 *  @param touchX The x-coordinate of a touch event in screen space.
	 * @return The pressed thumb or null if none has been touched.
	 */
	private Thumb evalPressedThumb(float touchX) {
		Thumb result = null;
		boolean minThumbPressed = isInThumbRange(touchX, minSelectedValue);
		boolean maxThumbPressed = isInThumbRange(touchX, maxSelectedValue);
		if (minThumbPressed && maxThumbPressed) {
			// if both thumbs are pressed (they lie on top of each other),
			// choose the one with more room to drag. this avoids "stalling" the
			// thumbs in a corner, not being able to drag them apart anymore.
			result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
		} else if (minThumbPressed)
			result = Thumb.MIN;
		else if (maxThumbPressed)
			result = Thumb.MAX;
		return result;
	}

	/** Decides if given x-coordinate in screen space needs to be interpreted as
	 * "within" the normalized thumb x-coordinate.
	 * @param touchX The x-coordinate in screen space to check.
	 * @param value normalized x-coordinate of the thumb to check.
	 * @return true if x-coordinate is in thumb range, false otherwise.
	 */
	private boolean isInThumbRange(float touchX, int value) {
		return Math.abs(touchX - valueToScreen(value)) <= thumbHalfWidth;
	}

	/** Converts a normalized value into screen space.
	 * @param coord The value to convert.
	 * @return The converted value in screen space.
	 */
	private int valueToScreen(int coord) {
		//android.util.Log.d(TAG, "W: "+String.valueOf(getWidth())+" - C: "+String.valueOf(coord));
		return padding+(getWidth()-2*padding)*(coord-minValue)/(maxValue-minValue);
	}
	private int screenToValue(float coord) {
		if(getWidth()<2*padding) return 0; // prevent division by zero
		int result=(int)coord*(maxValue-minValue)/(getWidth()-2*padding)+minValue;
		result=result<minValue?minValue:result;
		result=result>maxValue?maxValue:result;
		return result;
	}

	/** Callback listener interface to notify about changed range values.*/
	public interface OnRangeSeekBarChangeListener {
		public void onRangeSeekBarValuesChanged(RangeSeekbar bar, int minValue, int maxValue);
	}

	/** Thumb constants (min and max).  */
	private static enum Thumb {MIN, MAX};
	
	/** Min and max value displayed in the seek bar
	 * @param min The min value
	 * @param max The max value
	 */
	public void setRange(int min, int max) {
		minValue=min;
		maxValue=max;
		//android.util.Log.d(TAG, "Range: "+String.valueOf(minValue)+" - "+String.valueOf(maxValue));
		setSelectableRange(min, max);
		//invalidate();
	}
	/** Min and max value selectable by the user in the seek bar
	 * @param min The min value
	 * @param max The max value
	 */
	public void setSelectableRange(int min, int max) {
		minSelectableValue=min<minValue?minValue:min;
		maxSelectableValue=max>maxValue?maxValue:max;
		//android.util.Log.d(TAG, "Selectable range: "+String.valueOf(minSelectableValue)+" - "+String.valueOf(maxSelectableValue));
		minSelectedValue=minSelectableValue;
		maxSelectedValue=maxSelectableValue;
	}

	public int getMinValue() {return minValue;}
	public int getMaxValue() {return maxValue;}
	public int getMinSelectableValue() {return minSelectableValue;}
	public int getMaxSelectableValue() {return maxSelectableValue;}
	public int getMinSelectedValue() {return minSelectedValue;}
	public int getMaxSelectedValue() {return maxSelectedValue;}
	
}
